// FC+HOOKS 模拟类的方式，也模拟的 vue/mobx 的模式

import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react';
import { produce } from 'immer';
import { useDraft } from 'hooklib';
import s from './mimicVue3.less';

// 类似 vue3 的 composition API 用法，
// 可以直接用到组件中(如同 vue3 setup 方法)，
// 或者和其他的 hook 一起组合用到组件中
function useLikeClass() {

  //【data 数据状态】
  // 这里面是相当于 class 的实例变量，但是通过 setState(old=>new) 可以驱动引用的FC刷新
  // 相当于 vue 组件的 data，但是不带响应式改造，只是 plain object。
  // 数据变化的响应最终靠调用 setState 触发，不像 vue 需要提前创建大量的 watcher
  const [self, setDraft, setState] = useDraft(() => ({
    d1: 1,
    d2: 2,
    deep: {
      L1: {
        L2: {
          v: 100, // 非常深的值，但是也需要更新，需要通过 immer 简化代码
        }
      }
    }
  }));

  // 【methods 方法】
  // 这里面是相当于 class 的实例方法
  const methods = useMemo(() => {
    return {
      asSelector() {
        return self.d1 * self.d2;
      },
      asReducer(a = 0, b = 0) {
        setState(v => ({ ...v, d1: a, d2: b })); // 都用 reducer 方式
      },
      fib() {
        setState(v => ({ ...v, d1: v.d2, d2: v.d1 + v.d2 }));
      },
      newMethod() {
        // 所有 method 可以先写到一起，然后如果性能不好，再挪到各自的 useCallback 中定义
      }
    };
  }, [self]); // 依赖self保证状态变了，方法引用最新的状态，依赖粒度比较粗，有不必要性能损耗，优势就是代码量少

  // 方法也可以不放到 methods 里面，更新依赖更少；代码量比 useMemo多方法定义多，但是性能更好
  const callback1 = useCallback(() => {
    alert(self.d1);
  }, [self.d1]); // 精确依赖特定数据项

  const asDirectMutate = useCallback(() => {
    // 有 immer 加持，修改深层次数据项的 reducer 从原来很难写，变成如 vue/mobx 一样直接修改了
    setState(produce(v => {
      v.deep.L1.L2.v += 1;
    }));
  }, []);

  const asDirectMutate2 = useCallback(() => {
    // 有 immer 加持，修改深层次数据项的 reducer 从原来很难写，变成如 vue/mobx 一样直接修改了
    // 如同定义 mobx action()
    setDraft(v => {
      v.deep.L1.L2.v += 1;
    });
  }, []);

  // 【 computed 计算值 】
  // 可以达到 vue/mobx 的计算值的效果，省去了注册和触发watcher的内存和处理开销，当然只支持同步计算
  const computedSum = useMemo(() => self.d1 * self.d2, [self]); // 粗线条的依赖
  const computedSquare = useMemo(() => self.d1 * self.d1, [self.d1]); // 精确细致的依赖

  return [self, { ...methods, callback1, asDirectMutate, asDirectMutate2 }, {
    computedSum,
    computedSquare,
  }];
}

function useLog(d1: number) {
  // 【 watcher 变化监控】
  // 记录所有值变化历史的范例
  const [d1log, setd1Log] = useState(() => [] as number[]);
  const canceled = useRef([]);
  useEffect(() => {
    setd1Log(arr => ([...arr, d1]));
  }, [d1]); // 可以 watch 所有能访问到的数据，包括计算值，表达式结果
  const back = useCallback(() => {
    setd1Log((list) => {
      const last = list[list.length - 1];
      if (!last) {
        alert('日志已经空，不能再返回了！');
        return list;
      }
      canceled.current.push(last);
      return list.slice(0, -1);
    });
  }, []);
  const forward = useCallback(() => {
    const next = canceled.current.pop();
    if (!next) {
      alert('日志已经到头，不能再前进了！');
      return;
    }
    setd1Log((list) => ([...list, next]));
  }, []);
  return [d1log, back, forward];
}

/**
 * 组件用到的所有的数据状态，方法，计算值，watcher 都在 useHook 中提供
 * （也可以是多个组合提供，起到 mixin 的效果）
 * 组件本身只负责展示 hook 提供的数据，并且事件挂接 hook 提供的回调
 */
export default function MimicClass() {
  const [self, m, c] = useLikeClass();
  const [d1log, back, forward] = useLog(self.d1); // 这里是 hook 组合的范例，后面 hook的参数使用前面 hook的返回结果，和 vue composition API 一致
  return (
    <div>
      <div className={s.grid}>
        <span>d1: {self.d1} </span>
        <span>d2: {self.d2}</span>
        <span>computedSum: {c.computedSum}</span>
        <span>computedSquare: {c.computedSquare}</span>
        <span>m.asSelector(): {m.asSelector()}</span>
        <span>deep.L1.L2.v: {self.deep.L1.L2.v}</span>
      </div>

      <div className={s.grid}>
        <button onClick={() => m.asReducer(2, 3)}>call asReducer</button>
        <button onClick={m.asDirectMutate}>call asDirectMutate</button>
        <button onClick={m.asDirectMutate2}>call asDirectMutate2</button>
        <button onClick={m.fib}>call fib</button>
        <button onClick={m.callback1}>call callback1</button>
        <button onClick={back}>d1 history go back</button>
        <button onClick={forward}>d1 history go forward</button>
      </div>

      <ul className={s.left}>
        {d1log.map(item => (<li>{item}</li>))}
      </ul>

    </div>

  )
}
